
import os
import json
import strutils
import unicode
import encodings
#import unidecode
import nimlog

# 怎么好像没啥用
when defined(useRealtimeGC) and not defined(boehmgc) and not defined(gogc) and not defined(nogc):
    GC_setMaxPause(5000)

when NimMinor >= 19 and NimPatch >= 6:
    let supportedversion = true

proc Splitn*(s:string, n:int) : seq[string] =
    var rets: seq[string]
    var sub = newString(0)
    for c in s:
        sub.add(c)
        if sub.len() >= n:
            rets.add(sub)
            sub = newString(0)
    if strutils.strip(sub).len > 0:
        if sub.len < 5:
            ldebug("left sub", sub.len, sub[0], sub)
        rets.add(sub)
    return rets

proc Splitrn*(s:string, n:int) : seq[string] =
    var rets: seq[string]
    var sub = newString(0)
    for c in s.runes:
        sub.add($c)
        if sub.len() >= n:
            rets.add(sub)
            sub = newString(0)
    if unicode.strip(sub).len > 0: rets.add(sub)
    return rets

# // rune support, utf8 3byte, but ui width is 2
proc Splitrnui*(s:string, n:int) : seq[string] =
    var rets: seq[string]
    var sub = newString(0)
    var subuilen = 0
    for c in s.runes:
        let uilen = if c.size == 1: 1 else: 2
        if (subuilen + uilen) > n:
            rets.add(sub)
            sub = newString(0)
            subuilen = 0

        sub.add($c)
        subuilen += uilen
    if unicode.strip(sub).len > 0: rets.add(sub)
    return rets

# 2x faster of Splitrnui
# // rune support, utf8 3byte, but ui width is 2
proc Splitrnui2*(s:string, n:int) : seq[string] =
    var rets: seq[string]
    var poffset = 0
    var subuilen = 0
    var sublen = 0
    for c in s.runes:
        let uilen = if c.size == 1: 1 else: 2
        if (subuilen + uilen) > n:
            let sub = unicode.runeSubStr(s, poffset, sublen)
            rets.add(sub)
            poffset += sublen
            subuilen = 0
            sublen = 0

        subuilen += uilen
        sublen += 1
    if sublen > 0: rets.add(unicode.runeSubStr(s, poffset, sublen))
    return rets

# 3x faster of Splitrnui2, 6x of Splitrnui
# // rune support, utf8 3byte, but ui width is 2
# return is rune count, so use unicode.runeSubStr()
proc Splitrnui3*(s:string, n:int) : seq[int] =
    var rets: seq[int]
    var poffset = 0
    var subuilen = 0
    var sublen = 0
    for c in s.runes:
        let uilen = if c.size == 1: 1 else: 2
        if (subuilen + uilen) > n:
            rets.add(sublen)
            subuilen = 0
            poffset += sublen

        subuilen += uilen
        sublen += 1
    if sublen > 0: rets.add(sublen)
    return rets

proc tostr*(v:openArray[char]) : string = cast[string](v)
proc tou64*[T](v:ptr T) : ptr uint64 = cast[ptr uint64](v)
proc tou64*(v: Natural) : uint64 = cast[uint64](v)
proc toi64*(v: Natural) : int64 = cast[int64](v)
proc toi32*(v: Natural) : int32 = cast[int32](v)
proc tou32*(v: Natural) : uint32 = cast[uint32](v)
proc toptr*[T](v:ptr T) : pointer = cast[pointer](v)
proc toptr*(v:cstring) : pointer = cast[pointer](v)
proc tof32*(v: Natural) : float32 = return v.float # for float, cannot use cast, for cast is bitwise cast
proc tof64*(v: Natural) : float64 = return v.float
proc tojson*[T](v: T) : string = $(%*v)
proc `%`*(v:uint32) : JsonNode = newJInt(v.BiggestInt)
proc `$`*(v:uint32) : string = strutils.strip(v.repr())
proc `$`*(v:uint16) : string = strutils.strip(v.repr())
proc toaddr*(v: proc) : pointer = cast[pointer](v)
proc toproc(v: pointer) : proc() = cast[ptr proc()](v.unsafeAddr)[] # just demo how cast pointer to a proc
proc toaddr[T](v: ref T) : pointer = cast[pointer](v)
proc ntocstr(v: string) : cstring {.exportc.}= return v
proc ctonstr(v: cstring) : string {.exportc.} = $v
proc dtaddr*[T](v:openArray[T]) : pointer =
    return if v.len > 0: v[0].unsafeAddr else: nil
proc rtrim*(v:string, chars:set[char] = Whitespace) : string = v.strip(leading=false, chars=chars)
proc ltrim*(v:string, chars:set[char] = Whitespace) : string = v.strip(trailing=false, chars=chars)

# some case c use 0 as success, 1 as failed
proc ctrue0*(v : Natural) : bool = v == 0
proc cfalse0*(v : Natural) : bool = v == 1
proc ctrue1*(v : Natural) : bool = v == 1
proc cfalse1*(v : Natural) : bool = v == 0
proc addc*(v: var string, cstr: cstring, sz: int) =
    for i in 0..<sz: v.add cstr[i]
    return
proc addp*(v: var string, cstrp: pointer, sz: int) =
    addc(v, cast[cstring](cstrp), sz)
    return

#let ctrue = cint(1)
#let cfalse = cint(0)

import macros
import tables
# 让他像 newseq[int](40)
macro newarr*(T:typedesc, sz:int) : untyped =
    result = quote do:
        var arr :array[`sz`, `T`]; arr

macro newseq*(T:typedesc, sz:int) : untyped =
    result = quote do: newseq[T](sz)

macro newtable*(TK:typedesc, TV:typedesc) :untyped =
    result = quote do:
        var map = newTable[`TK`, `TV`](); map

macro pass*() : untyped =
    result = quote do: discard

#var buf = newarr(char, 100)
#ldebug(buf.len())
#var buf = newarr(int, 123)
#ldebug(buf.len())
#var buf = newarr(string, 123)
#ldebug(buf.len())
#var tab = newtable(int,  string)
#ldebug(tab.len())

#import json
import typeinfo
import typetraits

# 默认填充在obj有的字段，但jnode中没有的字段
# var obj = SomeTime()
# fixjsonnode(obj, jnode)
# jnode.to(obj)
proc fixjsonnode*[T](obj:var T, jnode:JsonNode)=
    # fields 需要作用于Tuple 或者 Object，ref不行，所以要.base
    var anyobj = obj.toany
    if anyobj.kind == akRef: anyobj = obj.toany.base
    elif anyobj.kind == akPtr: anyobj = obj.toany.base
    for x, y in anyobj.fields():
        if jnode.haskey(x): continue
        if y.kind == akInt64: jnode{x} = newJInt(0)
        elif y.kind == akUInt64: jnode{x} = newJInt(0)
        elif y.kind == akInt32: jnode{x} = newJInt(0)
        elif y.kind == akUInt32: jnode{x} = newJInt(0)
        elif y.kind == akInt16: jnode{x} = newJInt(0)
        elif y.kind == akUInt16: jnode{x} = newJInt(0)
        elif y.kind == akString: jnode{x} = newJString("")
        elif y.kind == akTuple: jnode{x} = newJObject()
        elif y.kind == akRef: jnode{x} = newJObject()
        else: linfo("Unknown", obj.type.name, y.kind)
    return

type
    AnyFace*[T] = object
        anyv*: typeinfo.Any
        refv*: T
    RawAny* = object
        value*: pointer
        when defined(js):
            rawType*: PNimType
        else:
            rawTypePtr*: pointer

proc toanyf*[T](x: T): AnyFace[T] =
    var vf = AnyFace[T](refv: x)
    vf.anyv = vf.refv.toAny
    return vf
proc asrany*(x: typeinfo.Any) : RawAny = cast[RawAny](x)

import times

# // 中文常用格式
proc totoday*(t: DateTime) : string = t.format("HH:mm:ss")
proc totodayminite*(t: DateTime): string = t.format("HH:mm")
# humanize
proc hmstr*(d:Duration) : string =
    var s = $d
    let units = {"nanoseconds": "ns", "microseconds": "us", "milliseconds": "ms",
                  "seconds": "s", "minutes": "m", "hours": "h", "days": "d", "weeks": "w",
                  "months": "M", "years": "y", "and": "", ",": "", " ":""}.toTable
    for k,v in units: s = s.replace(k, v)
    return s
# https://github.com/juancarlospaco/nim-bytes2human
proc hmbytes(d:SomeInteger) : string =
    return $d
proc hmstr*(v:SomeFloat) : string =
    var s = ($v)
    var dotpos = s.find(".")
    if dotpos == -1: return s
    return s.substr(0, dotpos+3).strip(chars={'0'}, leading=false)

import random
macro fclink*(args:varargs[untyped]) : untyped =
    result = quote do:
        if rand(567) == 890: echo `args`
# always false, but compiler don't known
proc itsfalse*() : bool = return rand(567) == 890

# since office nim version depcreated these re regex, make a copy here
const ## common regular expressions
    regIdentifier* = r"\b[a-zA-Z_]+[a-zA-Z_0-9]*\b"
    ## describes an identifier
    regNatural* = r"\b\d+\b"
    ## describes a natural number
    regInteger* = r"\b[-+]?\d+\b"
    ## describes an integer
    regHex* = r"\b0[xX][0-9a-fA-F]+\b"
    ## describes a hexadecimal number
    regBinary* = r"\b0[bB][01]+\b"
    ## describes a binary number (example: 0b11101)
    regOctal* = r"\b0[oO][0-7]+\b"
    ## describes an octal number (example: 0o777)
    regFloat* = r"\b[-+]?[0-9]*\.?[0-9]+([eE][-+]?[0-9]+)?\b"
    ## describes a floating point number
    regEmail* = r"\b[a-zA-Z0-9!#$%&'*+/=?^_`{|}~\-]+(?:\. &" &
        r"[a-zA-Z0-9!#$%&'*+/=?^_`{|}~-]+)*@" &
        r"(?:[a-zA-Z0-9](?:[a-zA-Z0-9-]*[a-zA-Z0-9])?\.)+" &
        r"(?:[a-zA-Z]{2}|com|org|net|gov|mil|biz|" &
        r"info|mobi|name|aero|jobs|museum)\b"
    ## describes a common email address
    regURL* = r"\b(http(s)?|ftp|gopher|telnet|file|notes|ms-help)" &
        r":((//)|(\\\\))+[\w\d:#@%/;$()~_?\+\-\=\\\.\&]*\b"
    ## describes an URL

type error* = ref object of RootObj
    s: string
    frame: StackTraceEntry
    p: error
    exc: ref Exception
proc caller*() : StackTraceEntry =
    var stks = getStackTraceEntries()
    return stks[stks.len - 3]
proc newError*(text: string) : error =
    return error(s: text, frame:  caller())
proc newErrorExc*() : error =
    let text = getCurrentExceptionMsg()
    return error(s: text, frame: caller(), exc: getCurrentException())
proc newErrorOs*() : error =
    let errno = osLastError()
    let text = "$# $#" % [$errno, osErrorMsg(errno)]
    return error(s: text, frame: caller(), exc: nil)
proc wrap*(err:error, text: string) : error =
    return error(s: text, frame: caller(), p: err)
proc unwrap*(err:error) : error =
    return err.p
proc isa*(err, target : error) : bool =
    var terr = err
    while true:
        if terr.frame == target.frame: return true
        terr = terr.unwrap()
        if terr == nil: return false
    return false
proc asa*(err:error, target: pointer) : bool =
    return false
proc `$`*(err:error) : string =
    if err == nil: return "nil"
    return err.s
proc okay*(err:error) : bool = err == nil
proc sure*(err:error) : bool = err != nil
proc detail*(err:error, hint: string = "") : string =
    if err == nil: return "nil"
    var terr = err
    var s = "Error stack trace: $#\n" % [hint]
    var depth = 0
    while terr != nil:
        s &= "#$# Error: $#\n    $# $#:$#\n" % [
            $depth, $terr, $terr.frame.procname, $terr.frame.filename, $terr.frame.line,]
        #s &= "#" & $depth & " Error: " & $terr & "\n    " & " " & $terr.frame.procname &
        #    " " & $terr.frame.filename & ":" & $terr.frame.line & "\n"
        terr = terr.unwrap()
        depth += 1
    return s
template newexc*(msg: string, parentException: ref Exception = nil, exceptn: typedesc = Exception) : untyped =
    newException(exceptn, msg, parentException)

#[
var err = newerror("heheh")
echo err
echo err.wrap("123").detail()

#0 Error: 123
     ornet ~/.nimble/mulib/ornet.nim:18
#1 Error: heheh
     ornet ~/.nimble/mulib/ornet.nim:16
]#

macro erreturn*(err:error) : untyped =
    result = quote do:
        if `err` != nil: return
macro errprint*(err:error, txt: string = "") : untyped =
    result = quote do:
        let tinfo = instantiationInfo()
        let sepos = tinfo.filename.rfind("/")
        let shortfname = tinfo.filename[sepos+1..tinfo.filename.len()-1]
        if `err` != nil:
            echo shortfname & ":" & $tinfo.line & " " & `txt` & " " & `err`.detail()

# compatiable with string and seq
type hackslice = ref object
    slen: int # => len
    scap: int # => reserved
    sdat: pointer # => data
# hacked capacity
proc cap*(s:string) : int = cast[hackslice](s).scap
proc cap*[T](s:seq[T]) : int = cast[hackslice](s).scap
proc cap*[T](s:set[T]) : int = cast[hackslice](s).scap
proc cap*[A,B](s:Table[A,B]) : int = cast[hackslice](s).scap
proc cap*[A,B](s:TableRef[A,B]) : int = cast[hackslice](s).scap


proc zero*(t: tuple) : auto = t[0]
proc one*(t: tuple) : auto = t[1]
proc two*(t: tuple) : auto = t[2]
proc three*(t: tuple) : auto = t[3]
proc four*(t: tuple) : auto = t[4]
proc five*(t: tuple) : auto = t[5]
proc six*(t: tuple) : auto = t[6]
proc len*(t: tuple) : int =
    for x in t.fields: result += 1

# seems impossible
# macro tail1*(t: untyped) : untyped =
#     echo treeRepr(t)
#     doAssert(t.typeKind == ntyTuple)
#     var tlen = `t`.len()
#     echo tlen, t.type
#     echo tlen, t.typeKind
#     for x in `t`.children:
#         echo x.repr

#     let rd = rand(1)
#     if rd == 0:
#         result = newFloatLitNode(123.0)
#     elif rd == 1:
#         result = newStrLitNode("sss")



{.push hint[XDeclaredButNotUsed]:off.}

